﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.ComponentModel.Composition;
using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Threading;

namespace UCL.Powersource
{
    public enum VerbosityLevels
    {
        Debug,
        Trace,
        Info,
        Warning,
        Error
    }

    public interface ILog
    {
        void Debug(string format, params object[] args);
        void Trace(string format, params object[] args);
        void Info(string format, params object[] args);
        void Warn(string format, params object[] args);
        void Warn(Exception exc, string format, params object[] args);
        void Error(Exception exc, string format, params object[] args);
        void Error(Exception exc);
    }

    [Export(typeof(ILog))]
    public class LogManager : IDisposableEx, ILog
    {

        private static LogManager _DefaultInstance;
        public static LogManager DefaultInstance
        {
            get
            {
                if (_DefaultInstance == null || _DefaultInstance.IsDisposed)
                {
                    _DefaultInstance = new LogManager();
                }
                return _DefaultInstance;
            }
        }
        public static Func<Type, ILog> GetLog = (t) => DefaultInstance;

        //public static ILog GetLogForType<T>() { return GetLog(typeof(T)) ?? DefaultInstance; }


        private List<ILog> Logs;
        public VerbosityLevels VerbosityLevel { get; private set; }
        //private static LogManager DefaultInstance = new LogManager();


        private LogManager(bool useDiagnosticsLog = true, VerbosityLevels verbosity = VerbosityLevels.Debug)
        {
            Logs = new List<ILog>();
            VerbosityLevel = verbosity;
            if (useDiagnosticsLog)
            {
                Logs.Add(new DiagnosticLog(VerbosityLevel));
            }
        }

        public ILog AddLog(ILog log)
        {
            if (!Logs.Contains(log))
            {
                Logs.Add(log);
                return log;
            }
            else
                return null;

        }

        public ILog RemoveLog(ILog log)
        {
            if (Logs.Contains(log))
            {
                Logs.Remove(log);
                return log;
            }
            else
                return null;
        }

        //public static void Debug(string format, params object[] args) { }
        //public static void Trace(string format, params object[] args) { }
        //public static void Info(string format, params object[] args) { }
        //public static void Warn(string format, params object[] args) { }
        //public static void Warn(Exception exc, string format, params object[] args) { }
        //public static void Error(Exception exc, string format, params object[] args) { }

        public void Dispose()
        {
            IsDisposed = true;
            foreach (var log in Logs)
            {
                var displog = log as IDisposable;
                if (displog != null) displog.Dispose();
            }

        }

        public void Debug(string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Debug(format, args);
            }
        }

        public void Trace(string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Trace(format, args);
            }
        }

        public void Info(string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Info(format, args);
            }
        }

        public void Warn(string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Warn(format, args);
            }
        }

        public void Warn(Exception exc, string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Warn(exc, format, args);
            }
        }

        public void Error(Exception exc, string format, params object[] args)
        {
            foreach (var log in Logs)
            {
                log.Error(exc, format, args);
            }
        }

        public void Error(Exception exc)
        {
            foreach (var log in Logs)
            {
                log.Error(exc);
            }
        }

        public bool IsDisposed
        {
            get;
            private set;
        }
    }

    public class CallBackLog : BaseLog, IDisposableEx
    {
        //ObservableCollection<string> ObservableStringCollection;
        //Dispatcher dispatcher;
        Action<string> _logMessageAction;

        public CallBackLog(VerbosityLevels vb, Action<string> logMessageAction)
            : base(vb)
        {
            //if (oc == null) throw new ArgumentNullException("oc");
            //if (dispatcher != null) this.dispatcher = dispatcher;
            //ObservableStringCollection = oc;
            _logMessageAction = logMessageAction;
        }
        protected override void EmitToTrace(VerbosityLevels level, Exception exc, string format, params object[] args)
        {
            if (IsDisposed || _logMessageAction == null) return;
            if (level >= this.VerbosityLevel)
            {
                _logMessageAction(base.ComposeMessage(level, exc, format, args));
            }
        }

        protected override void OnDisposing()
        {
            _logMessageAction = null;
            //ObservableStringCollection = null;
        }
    }

    public class FileLog : BaseLog, IDisposableEx
    {
        StreamWriter stream;

        public FileLog(VerbosityLevels verbosityLevel, Stream fileStream)
            : base(verbosityLevel)
        {
            //Trace("Constructing FileLog with stream : Verbosity {0}, Stream {1}", verbosityLevel, fileStream);
        }

        public FileLog(VerbosityLevels verbosityLevel, string fileName, bool append = true)
            : base(verbosityLevel)
        {
            if (append)
            {
                stream = System.IO.File.AppendText(fileName);
            }
            else
            {
                stream = System.IO.File.CreateText(fileName);
            }
            //Trace("Constructing FileLog : Verbosity {0}, filename {1}", verbosityLevel,fileName);
        }

        protected override void EmitToTrace(VerbosityLevels level, Exception exc, string format, params object[] args)
        {
            if (IsDisposed) return;
            string msg = string.Empty;
            if (format != null)
                msg = string.Format(format, args);
            if (exc != null)
            {
                var st = "";
                if (exc.StackTrace != null) st = exc.StackTrace.ToString();
                msg += string.Format("\r\n{0}-{1} at \r\n{2}", exc.GetType(), exc.Message, st);
            }
            try
            {
                if (stream != null)
                {
                    stream.WriteLine("{0} : {1}", level.ToString(), msg);
                    stream.Flush();
                }
            }
            catch (Exception)
            {
                
            }
            
        }

        protected override void EmitToTrace(VerbosityLevels level, string format, params object[] args)
        {
            EmitToTrace(level, null, format, args);
        }

        protected override void OnDisposing()
        {
            //Debug("{0}-{1}", this.ToString(), "Disposing.");
            if (stream != null)
            {
                stream.Dispose();
                stream = null;
            }
        }
    }

    public abstract class BaseLog : ILog, IDisposableEx
    {
        public BaseLog(VerbosityLevels verbosityLevel)
        {
            VerbosityLevel = verbosityLevel;
            //Trace("Constructing BaseLog : Verbosity {0}", verbosityLevel);
        }

        protected VerbosityLevels VerbosityLevel = VerbosityLevels.Debug;

        protected string ComposeMessage(VerbosityLevels level, Exception exc, string format, params object[] args)
        {
            var message = "Error - Unable to compose log message, message was lost";
            if (format != null)
            {
                try
                {
                    message = string.Format(format, args);
                    if (exc != null)
                    {
                        message += " " + exc.ToString();
                    }
                    message = level.ToString() + ": " + message;
                }
                catch (Exception exc1)
                {
                    message = "Error: Formatting log message for [" + level.ToString() + (format ?? "#NULL#") + "] throws an exception" + exc1.ToString();
                    //throw;
                }
            }
            return message;
        }

        protected abstract void EmitToTrace(VerbosityLevels level, Exception exc, string format, params object[] args);

        protected virtual void EmitToTrace(VerbosityLevels level, string format, params object[] args)
        {
            EmitToTrace(level, null, format, args);
        }

        public void Debug(string format, params object[] args) { EmitToTrace(VerbosityLevels.Debug, format, args); }
        public void Trace(string format, params object[] args) { EmitToTrace(VerbosityLevels.Trace, format, args); }
        public void Info(string format, params object[] args) { EmitToTrace(VerbosityLevels.Info, format, args); }
        public void Warn(string format, params object[] args) { EmitToTrace(VerbosityLevels.Warning, format, args); }
        public void Warn(Exception exc, string format, params object[] args) { EmitToTrace(VerbosityLevels.Warning, exc, format, args); }
        public void Error(Exception exc, string format, params object[] args) { EmitToTrace(VerbosityLevels.Error, exc, format, args); }
        public void Error(Exception exc) { EmitToTrace(VerbosityLevels.Error, exc, string.Empty); }

        protected abstract void OnDisposing();


        public void Dispose()
        {
            if (IsDisposed) return;
            OnDisposing();
            IsDisposed = true;
            GC.SuppressFinalize(this);
        }

        public bool IsDisposed { get; private set; }
    }

    public class DiagnosticLog : BaseLog, IDisposableEx
    {

        public DiagnosticLog(VerbosityLevels vl)
            : base(vl) { }

        protected override void EmitToTrace(VerbosityLevels level, Exception exc, string format, params object[] args)
        {
            if (level >= VerbosityLevel)
            {
                var message = ComposeMessage(level, exc, format, args);
                if (level == VerbosityLevels.Debug)
                {
                    System.Diagnostics.Debug.WriteLine(message);
                }
                else
                {
                    System.Diagnostics.Trace.WriteLine(message);
                }

            }
        }

        protected override void OnDisposing()
        {
            System.Diagnostics.Debug.Flush();
            System.Diagnostics.Trace.Flush();
        }

    }

    public class NullLog : ILog
    {
        public void Debug(string format, params object[] args) { }
        public void Trace(string format, params object[] args) { }
        public void Info(string format, params object[] args) { }
        public void Warn(string format, params object[] args) { }
        public void Warn(Exception exc, string format, params object[] args) { }
        public void Error(Exception exc, string format, params object[] args) { }
        public void Error(Exception exc) { }
    }
}
